package com.coffee.excel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.coffee.excel.metadata.MochaExcelField;
import com.coffee.excel.metadata.MochaMergeRow;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: songkui
 * @since: 2023/5/12 9:21
 */
@Slf4j
public abstract class MochaExportHandler<T> {

    /**
     * 导出 数据来源
     * 用户需要实现该接口 获取需要导出的数据；
     * @param queryJson  查询条件
     * @param fieldList  有需要合并单元格则需要 用到该参数
     * @return
     */
   public abstract List<T> findData(String queryJson, List<MochaExcelField> fieldList)throws MochaExcelException;




    /**
     * 合并单元格  Class 数据格式；
     * 注意： 不适合Map 数据结构；
     * @param data  数据
     * @param fieldList excel字段信息
     * @param order  排序： 多个逗号隔开； 比如需要合并信息 那么就必须已合并信息排序； 最好是比如主信息编号排序或者 主键ID排序；【 注意：必须是基本数据类型；Date 和对象字段不适合】
     * @param rule  合并规则： key: 判断数据是否相同字段【必须排序一致】 value:  那些字段需要合并； 比如：　所以公司字段信息；　已逗号隔开；【 注意：key必须是基本数据类型；Date 和对象字段不适合】
     * @param clazz　存储数据格式　class
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public  <T> List<T> doMerge(List<T> data, List<MochaExcelField> fieldList, String order, Map<String, String> rule, Class<T> clazz) throws IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();  //处理 字段
        Map<String, Field> fieldMap = toFieldMap(fields);
        List<Field> orderField = buildOrderField(fieldMap, order);
        //1, 排序
        data = data.stream().sorted(Comparator.comparing(o -> buildOrderValue(orderField, o))).collect(Collectors.toList()); //  必须先把数据根据合并要求排序
        //data.forEach(x -> System.out.println(JSON.toJSONString(x))); //

        //2, 构建 MergeRows
        Map<String, List<MochaMergeRow>> mergeRows = buildMergeRow(rule, fieldMap, data);
        for (MochaExcelField field : fieldList){
            for (Map.Entry<String, String> entry : rule.entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                List<MochaMergeRow> mergeList = mergeRows.get(k);
                if (CollectionUtil.isNotEmpty(mergeList) && v.contains(field.getFieldValue())){
                    field.setMergeRows(mergeList);
                }
            }
        }
        return data;

    }

    private  <T> Map<String, List<MochaMergeRow>> buildMergeRow(Map<String, String> rule, Map<String, Field> fieldMap, List<T> data) throws IllegalAccessException {
        Map<String, List<MochaMergeRow>> mergeRowMap = new HashMap<>();
        Map<String, Integer> first = new HashMap<>(); //缓存 合并start row
        Map<String, String> temp = new HashMap<>();  //缓存， 不同值

        for (Map.Entry<String, String> entry : rule.entrySet()) {
            String k = entry.getKey();
            mergeRowMap.put(k, new ArrayList<>());
            first.put(k, 0); // 默认从第一行开始
            temp.put(k, null);
        }

        int indexEnd = 0;
        int size = data.size();
        for (int i = 0; i < size; i++) {
            T o = data.get(i);
            for (Map.Entry<String, String> entry : rule.entrySet()) {
                String k = entry.getKey();
                String ov = getFieldValue(fieldMap, o, k);
                int firstTemp = first.get(k);

                if (!ov.equals(temp.get(k))) {
                    if ((i - 1 - firstTemp) > 0) {
                        mergeRowMap.get(k).add(MochaMergeRow.buildByIndex(firstTemp, (i - 1)));
                    }

                    first.put(k, i);
                }

                temp.put(k, ov);
            }

            indexEnd = i;
        }
        //最后一条
        for (Map.Entry<String, String> entry : rule.entrySet()) {
            String k = entry.getKey();
            int firstTemp = first.get(k);
            if ((indexEnd - firstTemp) > 0) {
                mergeRowMap.get(k).add(MochaMergeRow.buildByIndex(firstTemp, indexEnd));
            }
        }

        return mergeRowMap;

    }

    private Map<String, Field> toFieldMap(Field[] fields) {
        Map<String, Field> fieldMap = new HashMap<>(mapSize(fields.length));
        for (Field field : fields) {
            String fieldName = field.getName().toLowerCase();
            fieldMap.put(fieldName, field);
        }

        return fieldMap;
    }

    private List<Field> buildOrderField(Map<String, Field> fieldMap, String order) throws MochaExcelException {
        List<String> list = StrUtil.split(order, ",");
        List<Field> fieldList = new ArrayList<>(list.size());
        for (String f : list) {
            if (StrUtil.isBlank(f)) {
                throw new MochaExcelException("Mocha export excel order field error");
            }

            Field field = fieldMap.get(f.trim().toLowerCase());
            if (null == field) {
                throw new MochaExcelException("Mocha export excel order field error");
            }

            fieldList.add(field);
        }

        return fieldList;

    }

    private <T> String buildOrderValue(List<Field> fields, T o) {
        String str = "";
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                Object v = field.get(o);
                str += null == v ? "_" : v + "_";
            }
        } catch (IllegalAccessException e) {
            log.error("Build Order value error:", e);
            throw new MochaExcelException("Build Order value error: " + e.getMessage());
        }
        return str;
    }


    private <T> String getFieldValue(Map<String, Field> fieldMap, T o, String fieldName) throws IllegalAccessException {
        Field field = fieldMap.get(fieldName.trim().toLowerCase());
        if (null == field) {
            throw new MochaExcelException("Mocha export excel rule field error");
        }
        field.setAccessible(true);
        String v = Optional.ofNullable(field.get(o)).map(fo -> String.valueOf(fo)).orElse(null); //随机数为了 去重
        return StrUtil.isBlank(v) ? RandomUtil.randomString(8) : v;
    }


    /**
     * 合并单元格  Map 数据格式；
     */
    public  List<Map> doMergeByMap(List<Map> data, List<MochaExcelField> fieldList, String order, Map<String, String> rule) {
        //1, 排序
        String[] orders = order.split(",");
        data = data.stream().sorted(Comparator.comparing(o -> buildOrderValue(orders, o))).collect(Collectors.toList()); //  必须先把数据根据合并要求排序

        Map<String, List<MochaMergeRow>> mergeRows = buildMergeRow(rule, data);
        for (MochaExcelField field : fieldList){
            for (Map.Entry<String, String> entry : rule.entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                List<MochaMergeRow> mergeList = mergeRows.get(k);
                if (CollectionUtil.isNotEmpty(mergeList) && v.contains(field.getFieldValue())){
                    field.setMergeRows(mergeList);
                }
            }
        }
        return data;

    }

    private String buildOrderValue(String[] orders, Map m){
        String str = "";
        for (String order : orders){
            if (StrUtil.isBlank(order)){
                throw new MochaExcelException("Mocha export excel order field error");
            }
            Object v = m.get(order);
            str += null == v ? "_" : v + "_";
        }

        return str;
    }


    private   Map<String, List<MochaMergeRow>> buildMergeRow(Map<String, String> rule,  List<Map> data) {
        Map<String, List<MochaMergeRow>> mergeRowMap = new HashMap<>();
        Map<String, Integer> first = new HashMap<>(); //缓存 合并start row
        Map<String, String> temp = new HashMap<>();  //缓存， 不同值

        for (Map.Entry<String, String> entry : rule.entrySet()) {
            String k = entry.getKey();
            mergeRowMap.put(k, new ArrayList<>());
            first.put(k, 0); // 默认从第一行开始
            temp.put(k, null);
        }

        int indexEnd = 0;
        int size = data.size();
        for (int i = 0; i < size; i++) {
            Map o = data.get(i);
            for (Map.Entry<String, String> entry : rule.entrySet()) {
                String k = entry.getKey();
                String ov = Optional.ofNullable(o.get(k)).map(fo -> String.valueOf(fo)).orElse(null);
                ov = StrUtil.isBlank(ov) ? RandomUtil.randomString(8) : ov;
                int firstTemp = first.get(k);

                if (!ov.equals(temp.get(k))) {
                    if ((i - 1 - firstTemp) > 0) {
                        mergeRowMap.get(k).add(MochaMergeRow.buildByIndex(firstTemp, (i - 1)));
                    }

                    first.put(k, i);
                }

                temp.put(k, ov);
            }

            indexEnd = i;
        }
        //最后一条
        for (Map.Entry<String, String> entry : rule.entrySet()) {
            String k = entry.getKey();
            int firstTemp = first.get(k);
            if ((indexEnd - firstTemp) > 0) {
                mergeRowMap.get(k).add(MochaMergeRow.buildByIndex(firstTemp, indexEnd));
            }
        }

        return mergeRowMap;

    }

    private static int mapSize(int size){
        return (int) (size/0.75+1);
    }

}
